﻿using System;
using System.Collections.Generic;
using Lightray.Hattrick.ChppLib.EntityTypes;
using System.Linq;

namespace Lightray.Hattrick.ChppLib
{
    public class ProgressReport
    {
        private static IDictionary<BasicPlayer, IEnumerable<ProgressRecord>> _report;
        private static List<ProgressRecord> _records;

        public static IDictionary<BasicPlayer, IEnumerable<ProgressRecord>> GetTeamProgressReport(Team newTeam, Team oldTeam)
        {
            return GetTeamProgressReport(newTeam.Players, oldTeam.Players);
        }

        public static IDictionary<BasicPlayer, IEnumerable<ProgressRecord>> GetTeamProgressReport(EntityCollection<Player> newTeam, EntityCollection<Player> oldTeam)
        {
            List<BasicPlayer> newTeamPlayers = new List<BasicPlayer>();
            foreach (var p in newTeam)
                newTeamPlayers.Add(p);

            List<BasicPlayer> oldTeamPlayers = new List<BasicPlayer>();
            foreach (var p in oldTeam)
                oldTeamPlayers.Add(p);

            return GetTeamProgressReport(newTeamPlayers.ToEntityCollection(), oldTeamPlayers.ToEntityCollection());
        }

        public static IDictionary<BasicPlayer, IEnumerable<ProgressRecord>> GetTeamProgressReport(EntityCollection<YouthPlayer> newTeam, EntityCollection<YouthPlayer> oldTeam)
        {
            List<BasicPlayer> newTeamPlayers = new List<BasicPlayer>();
            foreach (var p in newTeam)
                newTeamPlayers.Add(p);

            List<BasicPlayer> oldTeamPlayers = new List<BasicPlayer>();
            foreach (var p in oldTeam)
                oldTeamPlayers.Add(p);

            return GetTeamProgressReport(newTeamPlayers.ToEntityCollection(), oldTeamPlayers.ToEntityCollection());
        }

        public static IDictionary<BasicPlayer, IEnumerable<ProgressRecord>> GetTeamProgressReport(EntityCollection<BasicPlayer> newTeam, EntityCollection<BasicPlayer> oldTeam)
        {
            _report = new Dictionary<BasicPlayer, IEnumerable<ProgressRecord>>();

            foreach (BasicPlayer p in newTeam)
            {
                 _records = new List<ProgressRecord>();

                if (playerIsNew(oldTeam, p))
                {
                    _records.Add(ProgressRecord.PlayerJoinedTeam());
                    continue;
                }

                BasicPlayer oldPlayer = oldTeam.ById(p.ID);
                reportTrainingProgress(p, oldPlayer);
                reportGeneralProgress(p, oldPlayer);

                if (p is Player) // no scores for youth players yet
                    reportRoleScoreProgress(p as Player, oldPlayer as Player);

                // placing this at the end of the loop, insures the PlayerLeft event is always the last event for a player
                if (playerNoLongerPartOfTeam(newTeam, p))
                    _records.Add(ProgressRecord.PlayerLeftTeam());

                _report[p] = _records;
            }
          
            return _report;
        }
  
        private static void reportGeneralProgress(BasicPlayer newPlayerData, BasicPlayer oldPlayerData)
        {
            var SENIOR_PLAYERS_EXCLUSIVE_ATTRIBUTES = new[] { ProgressAttributes.Salary, ProgressAttributes.TSI };
            var generalAttributes = new[] { ProgressAttributes.Age,
                                            ProgressAttributes.Cards, 
                                            ProgressAttributes.InjuryLevel, 
                                            ProgressAttributes.Salary, 
                                            ProgressAttributes.TSI };
            
            // youth players dont carry these
            if (newPlayerData is YouthPlayer)
                generalAttributes = generalAttributes.Except(SENIOR_PLAYERS_EXCLUSIVE_ATTRIBUTES).ToArray();

            foreach (ProgressAttributes attribute in generalAttributes)
            {
                switch (compareGeneralAttribute(newPlayerData, oldPlayerData, attribute))
                {
                    case 1: // newPlayer is better
                        _records.Add(ProgressRecord.PositiveGeneralProgress(attribute));
                        break;
                    case -1: // oldPlayer is better
                        _records.Add(ProgressRecord.NegativeGeneralProgress(attribute));
                        break;
                    default: // no change
                        break;
                }
            }       
        }

        private static void reportRoleScoreProgress(Player newPlayerData, Player oldPlayerData)
        {
            foreach (PlayerRole r in Enum.GetValues(typeof(PlayerRole)))
            {
                PlayerScores oldPlayerScores = new PlayerScores(oldPlayerData);
                PlayerScores newPlayerScores = new PlayerScores(newPlayerData);
                switch (compareRoleScoreAttribute(newPlayerScores, oldPlayerScores, r))
                {
                    case 1: // newPlayer is better
                        _records.Add(ProgressRecord.PositiveAbilityProgress(r));
                        break;
                    case -1: // oldPlayer is better
                        _records.Add(ProgressRecord.NegativeAbilityProgress(r));
                        break;
                    default: // no change
                        break;
                }
            }
        }        
        
        private static void reportTrainingProgress(BasicPlayer newPlayerData, BasicPlayer oldPlayerData)
        {
            foreach (Skill s in Enum.GetValues(typeof(Skill)))
            {
                switch (compareSkillAttribute(newPlayerData, oldPlayerData, s))
                {
                    case 1: // newPlayer is better
                        _records.Add(ProgressRecord.PositiveTrainingProgress(s));
                        break;
                    case -1: // oldPlayer is better
                        _records.Add(ProgressRecord.NegativeTrainingProgress(s));
                        break;
                    default: // no change
                        break;
                }
            }
        }

        private static int compareGeneralAttribute(BasicPlayer newPlayerData, BasicPlayer oldPlayerData, ProgressAttributes attribute)
        {
            // if tsi / salary is to be compared, BasicPlayer is assumed to be of Player type.
            switch (attribute)
            {
                // bigger is better attributes 
                case ProgressAttributes.TSI:
                    return (newPlayerData as Player).TSI.CompareTo((oldPlayerData as Player).TSI);

                // smaller is better
                case ProgressAttributes.Age:
                    return oldPlayerData.Age.Years.CompareTo(newPlayerData.Age.Years);
                case ProgressAttributes.Cards:
                    return oldPlayerData.Cards.CompareTo(newPlayerData.Cards);
                case ProgressAttributes.InjuryLevel:
                    return oldPlayerData.InjuryLevel.CompareTo(newPlayerData.InjuryLevel);                
                case ProgressAttributes.Salary:
                    return (oldPlayerData as Player).Salary.CompareTo((newPlayerData as Player).Salary);

                default:
                    throw new Exception("Invalid attribute to compare");
            }
        }

        private static int compareSkillAttribute(BasicPlayer newPlayer, BasicPlayer oldPlayer, Skill s)
        {
            int newValue = (int)newPlayer.GetSkillLevel(s);
            int oldValue = (int)oldPlayer.GetSkillLevel(s);

            return newValue.CompareTo(oldValue);                
        }

        private static int compareRoleScoreAttribute(PlayerScores newPlayer, PlayerScores oldPlayer, PlayerRole role)
        {
            double newScore = newPlayer.GetScoreByAbility(role);
            double oldScore = oldPlayer.GetScoreByAbility(role);

            return newScore.CompareTo(oldScore);
        }
       
        private static bool playerNoLongerPartOfTeam(EntityCollection<BasicPlayer> newTeam, BasicPlayer p)
        {
            return !newTeam.Contains(p);
        }

        private static bool playerIsNew(EntityCollection<BasicPlayer> oldTeam, BasicPlayer p)
        {
            return !oldTeam.Contains(p);
        }
    }
}